package ru.galiauir.engine;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.commons.lang.StringUtils;

import ru.galiauir.gui.ResultForm;
import ru.galiauir.main.Runner;
import ru.galiauir.utils.IOUtils;
import ru.galiauir.utils.RandomUtils;

public class Simulator {

	public static final String MONTHLY_DATA_FILE = Runner.MONTHLY_DATA_FILE;
	public static final String INVITATION_DATA_FILE = Runner.INVITATION_DATA_FILE;

	public static void simulate(float alpha, int maxDays, int initUsers,
			float beta, long averageAmount, long dispersia, int yRatio,
			float basicCost, boolean isConsoleMode) {
		simulate(alpha, maxDays, initUsers, beta, averageAmount, dispersia,
				yRatio, basicCost, isConsoleMode, false, false, null, null);
	}

	public static void simulate(float alpha, int maxDays, int initUsers,
			float beta, long averageAmount, long dispersia, int yRatio,
			float basicCost, boolean isConsoleMode, boolean logOnlyLast, boolean disableLogs, OutputStream mos, OutputStream inviteOs) {
		String dir;
		if (!disableLogs) {
			dir = "./output";
			dir = dir + "_" + RandomUtils.nextSessionId();
		} else {
			dir = System.getProperty("os.name").toLowerCase().contains("win") ? Runner.NO_LOGGED_OUTPUT_DIR_WIN : Runner.NO_LOGGED_OUTPUT_DIR_UNIX;
		}

		if (!disableLogs) {
			File fldr = new File(dir);

			fldr.mkdir();
		}

		Engine eng = new Engine(alpha, beta, averageAmount, dispersia, yRatio,
				basicCost);

		eng.init(initUsers);

		long prevUserId = eng.getMaxUserId();
		OutputStream os = null;
		long prevTimeStamp;
		DateFormat df = new SimpleDateFormat("HH:mm:ss.SSS");
		// ProgressForm pf = null;

		// if (!isConsoleMode && false) {
		// pf = new ProgressForm(maxDays);
		// }


		OutputStream monthlyOS = null;
		PrintWriter monthlyPW = null;
		try {
			monthlyOS = mos == null ? new BufferedOutputStream(new FileOutputStream(dir + "/"
					+ MONTHLY_DATA_FILE, true), 256) : mos;
			monthlyPW = new PrintWriter(monthlyOS);
			monthlyPW.println(StringUtils.rightPad("Alpha", 15)
							+ StringUtils.rightPad("Y", 7)
							+ StringUtils.rightPad("Month", 7)
							+ StringUtils.rightPad("Monthly costs", 16)
							+ StringUtils.rightPad("Monthly bonus spent", 22)
							+ "Monthly revenue");
		} catch (Exception e) {
			System.out.println("Failed to create file for monthly data output");
			e.printStackTrace();
		}

		for (int i = 0; i < maxDays; i++) {

			// if (pf != null && (i % 100 == 0)) {
			// pf.reRender(i);
			// }

			prevTimeStamp = System.currentTimeMillis();
			if (!disableLogs) {
				System.out.println("Starting day " + (i + 1) + " "
						+ df.format(new Date(System.currentTimeMillis())));
			}

			if ((i + 1) % 30 == 0 && monthlyPW != null) {
				try {
					monthlyPW.println(StringUtils.rightPad(alpha + "", 15)
									+ StringUtils.rightPad(yRatio + "", 7)
									+ StringUtils.rightPad(((i + 1) / 30) + "", 7)
									+ StringUtils.rightPad(eng.getMonthCost() + "", 16)
									+ StringUtils.rightPad(eng.getMonthBonusSpent()+ "", 22)
									+ eng.getMonthRevenue());
				} catch (Exception e) {
					e.printStackTrace();
				}
			}

			eng.executePurchase(i + 1);
			prevUserId = eng.executeDay(prevUserId);
			if (!disableLogs) {
				System.out.println(String.format(
						"Finished day %d, %d time elapsed", i + 1,
						System.currentTimeMillis() - prevTimeStamp));
			}
			if (!disableLogs && (!logOnlyLast || logOnlyLast && (i == (maxDays - 1)))) {
				try {
					os = new BufferedOutputStream(new FileOutputStream(dir
							+ "/day_" + (i + 1) + ".txt"), 128);
					PrintWriter pw = new PrintWriter(os);
					pw.println(String.format("%d users", eng.getMaxUserId() + 1));
					pw.flush();
					eng.getRoot().printMyself(pw, 0, "", false);
					pw.flush();
				} catch (Exception e) {
					System.out.println("Failed to create file \"" + dir
							+ "/day_" + i + ".txt" + "\"\n" + e);
				} finally {
					IOUtils.closeSilent(os);
				}
			}
			if (i == (maxDays - 1)) {
				OutputStream invOs = null;
				try {
				invOs = inviteOs == null ? new BufferedOutputStream(new FileOutputStream(dir + "/"
						+ INVITATION_DATA_FILE, true), 1024) : inviteOs;
				logInvitationData(alpha, yRatio, eng, initUsers, invOs);
				} catch (Exception e) {
					System.out.println("Failed to write invitation data");
					e.printStackTrace();
				} finally {
					if (inviteOs == null) {
						IOUtils.closeSilent(invOs);
					}
				}
			}
		}

		// if (pf != null) {
		// pf.dispose();
		// }

		if (!disableLogs) {
			PrintWriter finalStream = null;
			try {
				finalStream = new PrintWriter(new BufferedOutputStream(
						new FileOutputStream(dir + "/final.txt"), 128));
				finalStream.println("Average purchase: "
						+ (eng.getTotalPurchaseAmount() / eng
								.getTotalPurchaseNumber()));
				finalStream.println("Total bonus given: "
						+ eng.getTotalBonusGiven());
				finalStream.println("Total bonus spent: "
						+ eng.getTotalBonusSpent());
				finalStream.flush();
				finalStream.close();
			} catch (Exception e) {
				System.out.println("Exception: " + e);
			} finally {
				IOUtils.closeSilent(finalStream);
			}
		}

		if (monthlyOS != null) {
			monthlyPW.flush();
			if (mos == null) {
				if (monthlyPW != null) {
					monthlyPW.close();
				}
				try {
					monthlyOS.flush();
					monthlyOS.close();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}

		if (!isConsoleMode) {
			@SuppressWarnings("unused")
			ResultForm rf = new ResultForm(eng.getTotalPurchaseAmount()
					/ eng.getTotalPurchaseNumber(), eng.getTotalBonusGiven(),
					eng.getTotalBonusSpent());
		}

	}

	private static void logInvitationData(float alpha, int yRatio, Engine eng, long initUsers, OutputStream os) {
		int invitedNumber = 0;

		if (eng.getUserMap() != null) {
			for (long i = 0; i < initUsers; i++) {
				invitedNumber += eng.getUserMap().get(i).getNumberOfKids();
			}
		}

		BigDecimal totalInvited = new BigDecimal(eng.getUserMap().size() - initUsers);
		BigDecimal init = new BigDecimal(initUsers);
		BigDecimal percent = new BigDecimal(invitedNumber).setScale(10);

		percent = percent.divide(totalInvited, RoundingMode.HALF_DOWN).divide(init, RoundingMode.HALF_DOWN);

		PrintWriter pw = new PrintWriter(os);
		pw.println(StringUtils.rightPad(alpha + "", 15)
			     + StringUtils.rightPad(yRatio + "", 7)
			     + percent.toPlainString());

		pw.flush();

	}

}
